/*!	 synfig/rendering/renderer.cpp
**	 Renderer
**
**	......... ... 2015 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include <cstdlib>
#include <climits>

#include <typeinfo>

#include <synfig/general.h>
#include <synfig/localization.h>
#include <synfig/debug/debugsurface.h>
#include <synfig/debug/log.h>
#include <synfig/debug/measure.h>

#include "renderer.h"
#include "renderqueue.h"

#include "software/renderersw.h"
#include "software/rendererdraftsw.h"
#include "software/rendererlowressw.h"
#include "software/renderersafe.h"
#include "common/task/taskcallback.h"
#ifdef WITH_OPENGL
#include "opengl/renderergl.h"
#include "opengl/task/taskgl.h"
#endif

#endif

using namespace synfig;
using namespace rendering;

#ifdef _DEBUG
// #define DEBUG_TASK_LIST
// #define DEBUG_TASK_MEASURE
// #define DEBUG_OPTIMIZATION
// #define DEBUG_OPTIMIZATION_EACH_CHANGE
// #define DEBUG_OPTIMIZATION_MEASURE
// #define DEBUG_OPTIMIZATION_COUNTERS
#endif

Renderer::Handle Renderer::blank;
std::map<String, Renderer::Handle> *Renderer::renderers;
RenderQueue *Renderer::queue;
Renderer::DebugOptions Renderer::debug_options;
long long Renderer::last_registered_optimizer_index = 0;

void
Renderer::initialize_renderers()
{
    // initialize renderers
    RendererSW::initialize();
#ifdef WITH_OPENGL
    RendererGL::initialize();
#endif

    // register renderers
    register_renderer("software", new RendererSW());
    register_renderer("software-draft", new RendererDraftSW());
    register_renderer("software-low2",  new RendererLowResSW(2));
    register_renderer("software-low4",  new RendererLowResSW(4));
    register_renderer("software-low8",  new RendererLowResSW(8));
    register_renderer("software-low16", new RendererLowResSW(16));
    register_renderer("safe", new RendererSafe());
#ifdef WITH_OPENGL
    register_renderer("gl", new RendererGL());
#endif
}

void
Renderer::deinitialize_renderers()
{
#ifdef WITH_OPENGL
    RendererGL::deinitialize();
#endif
    RendererSW::deinitialize();
}

Renderer::~Renderer() { }

int
Renderer::get_max_simultaneous_threads() const
{
    assert(queue);
    return queue->get_threads_count() - 1;
}

bool
Renderer::is_optimizer_registered(const Optimizer::Handle &optimizer) const
{
    if (!optimizer)
        for (Optimizer::List::const_iterator i = optimizers[optimizer->category_id].begin(); i != optimizers[optimizer->category_id].end(); ++i)
            if (*i == optimizer) {
                return true;
            }

    return false;
}

void
Renderer::register_optimizer(Real order, const Optimizer::Handle &optimizer)
{
    if (optimizer) {
        assert(!is_optimizer_registered(optimizer));
        optimizer->order = order;
        optimizer->index = ++last_registered_optimizer_index;
        Optimizer::List &list = optimizers[optimizer->category_id];
        list.push_back(optimizer);
        std::sort(list.begin(), list.end(), Optimizer::less);
    }
}

void
Renderer::register_optimizer(const Optimizer::Handle &optimizer)
{
    if (optimizer) {
        register_optimizer(optimizer->order, optimizer);
    }
}

void
Renderer::unregister_optimizer(const Optimizer::Handle &optimizer)
{
    for (Optimizer::List::iterator i = optimizers[optimizer->category_id].begin(); i != optimizers[optimizer->category_id].end();)
        if (*i == optimizer) {
            i = optimizers[optimizer->category_id].erase(i);
        } else {
            ++i;
        }
}

void
Renderer::optimize_recursive(
    const Optimizer::List &optimizers,
    const Optimizer::RunParams& params,
    int &calls_count,
    int &optimizations_count,
    int max_level) const
{
    if (!params.ref_task) {
        return;
    }

    // check dependencies
    if (params.ref_affects_to & params.depends_from) {
        return;
    }

    // run all non-deep-first optimizers for current task
    // before processing of sub-tasks (see Optimizer::deep_first)
    for (Optimizer::List::const_iterator i = optimizers.begin(); i != optimizers.end(); ++i) {
        if (!(*i)->deep_first) {
            if ((*i)->for_task || ((*i)->for_root_task && !params.parent)) {
                // run
                Optimizer::RunParams p(params);
                (*i)->run(p);

                ++calls_count;

                if (params.ref_task != p.ref_task) {
                    ++optimizations_count;
#ifdef DEBUG_OPTIMIZATION_EACH_CHANGE
                    log("", params.list, (typeid(**i).name() + 19), &p);
#endif
                }

                // apply params
                params.ref_affects_to |= p.ref_affects_to;
                params.ref_mode |= p.ref_mode;
                params.ref_task = p.ref_task;

                // return when current task removed
                if (!params.ref_task) {
                    return;
                }

                assert(params.ref_task->check());

                // check dependencies
                if (params.ref_affects_to & params.depends_from) {
                    return;
                }
            }
        }
    }

    // process sub-tasks, only for non-for-root-task optimizers (see Optimizer::for_root_task)
    if (max_level > 0) {
        bool task_clonned = false;
        bool nonrecursive = false;
        bool recursive = false;
        Optimizer::RunParams initial_params(params);

        for (Task::List::iterator i = params.ref_task->sub_tasks.begin(); i != params.ref_task->sub_tasks.end();) {
            if (*i) {
                // recursive run
                initial_params.ref_task = params.ref_task;
                Optimizer::RunParams sub_params = initial_params.sub(*i);
                optimize_recursive(optimizers, sub_params, calls_count, optimizations_count, nonrecursive ? 0 : recursive ? INT_MAX : max_level - 1);
                nonrecursive = false;
                recursive = false;

                // replace sub-task if optimized
                if (sub_params.ref_task != *i) {
                    // before replacement clone current task (if it not already cloned)
                    if (!task_clonned) {
                        int index = i - params.ref_task->sub_tasks.begin();
                        params.ref_task = params.ref_task->clone();
                        i = params.ref_task->sub_tasks.begin() + index; // validate iterator
                        task_clonned = true;
                    }

                    *i = sub_params.ref_task;

                    // go to next sub-task if we don't need to repeat optimization (see Optimizer::MODE_REPEAT)
                    if ((sub_params.ref_mode & Optimizer::MODE_REPEAT_LAST) == Optimizer::MODE_REPEAT_LAST) {
                        // check recursive flag (see Optimizer::MODE_RECURSIVE)
                        if (sub_params.ref_mode & Optimizer::MODE_RECURSIVE) {
                            recursive = true;
                        } else {
                            nonrecursive = true;
                        }
                    } else {
                        ++i;
                    }
                } else {
                    // go to next sub-task, when sub-task not unchanged
                    ++i;
                }

                // apply affected categories
                params.ref_affects_to |= sub_params.ref_affects_to;

                // if mode is REPEAT_BRUNCH then provide this flag to result
                if ((sub_params.ref_mode & Optimizer::MODE_REPEAT_BRUNCH) == Optimizer::MODE_REPEAT_BRUNCH) {
                    params.ref_mode |= Optimizer::MODE_REPEAT_BRUNCH;
                    params.ref_mode |= (sub_params.ref_mode & Optimizer::MODE_RECURSIVE);
                } else

                    // if mode is REPEAT_PARENT then provide flag REPEAT_LAST to result (repeat only one upper level)
                    if ((sub_params.ref_mode & Optimizer::MODE_REPEAT_PARENT) == Optimizer::MODE_REPEAT_PARENT) {
                        params.ref_mode |= Optimizer::MODE_REPEAT_LAST;
                        params.ref_mode |= (sub_params.ref_mode & Optimizer::MODE_RECURSIVE);
                    }

                // check dependencies
                if (params.ref_affects_to & params.depends_from) {
                    return;
                }
            } else {
                // skip nulls
                ++i;
            }
        }
    }

    // run deep-first optimizers for current task
    // when all sub-tasks are processed (see Optimizer::deep_first)
    for (Optimizer::List::const_iterator i = optimizers.begin(); i != optimizers.end(); ++i) {
        if ((*i)->deep_first) {
            if ((*i)->for_task || ((*i)->for_root_task && !params.parent)) {
                // run
                Optimizer::RunParams p(params);
                (*i)->run(p);

                ++calls_count;

                if (params.ref_task != p.ref_task) {
                    ++optimizations_count;
#ifdef DEBUG_OPTIMIZATION_EACH_CHANGE
                    log("", params.list, (typeid(**i).name() + 19), &p);
#endif
                }

                // apply params
                params.ref_affects_to |= p.ref_affects_to;
                params.ref_mode |= p.ref_mode;
                params.ref_task = p.ref_task;

                // return when current task removed
                if (!params.ref_task) {
                    return;
                }

                assert(params.ref_task->check());

                // check dependencies
                if (params.ref_affects_to & params.depends_from) {
                    return;
                }
            }
        }
    }

}

void
Renderer::optimize(Task::List &list) const
{
    // debug::Measure t("Renderer::optimize");

    int current_category_id = 0;
    int current_optimizer_index = 0;
    Optimizer::Category current_affected = 0;
    Optimizer::Category categories_to_process = Optimizer::CATEGORY_ALL;
    Optimizer::List single(1);

    while (categories_to_process &= Optimizer::CATEGORY_ALL) {
        if (current_category_id >= Optimizer::CATEGORY_ID_COUNT) {
            current_category_id = 0;
            current_optimizer_index = 0;
            current_affected = 0;
            continue;
        }

        if (!((1 << current_category_id) & categories_to_process)) {
            ++current_category_id;
            current_optimizer_index = 0;
            current_affected = 0;
            continue;
        }

        if (current_optimizer_index >= (int)optimizers[current_category_id].size()) {
            categories_to_process &= ~(1 << current_category_id);
            categories_to_process |= current_affected;
            ++current_category_id;
            current_optimizer_index = 0;
            current_affected = 0;
            continue;
        }

        bool simultaneous_run = Optimizer::categories_info[current_category_id].simultaneous_run;
        const Optimizer::List &current_optimizers = simultaneous_run ? optimizers[current_category_id] : single;

        if (!simultaneous_run) {
            single.front() = optimizers[current_category_id][current_optimizer_index];
            Optimizer::Category depends_from_self = (1 << current_category_id) & single.front()->depends_from;

            if (current_affected & depends_from_self) {
                current_category_id = 0;
                current_optimizer_index = 0;
                current_affected = 0;
                continue;
            }
        }

        Optimizer::Category depends_from = 0;
        bool for_list = false;
        bool for_task = false;
        bool for_root_task = false;

        for (Optimizer::List::const_iterator i = current_optimizers.begin(); i != current_optimizers.end(); ++i) {
            depends_from |= ((1 << current_category_id) - 1) & (*i)->depends_from;

            if ((*i)->for_list) {
                for_list = true;
            }

            if ((*i)->for_task) {
                for_task = true;
            }

            if ((*i)->for_root_task) {
                for_root_task = true;
            }
        }

#ifdef DEBUG_OPTIMIZATION
        log("", list, etl::strprintf("before optimize category %d index %d", current_category_id, current_optimizer_index));
#endif

#ifdef DEBUG_OPTIMIZATION_MEASURE
        debug::Measure t(etl::strprintf("optimize category %d index %d", current_category_id, current_optimizer_index));
#endif

        if (for_list) {
            for (Optimizer::List::const_iterator i = current_optimizers.begin(); !(categories_to_process & depends_from) && i != current_optimizers.end(); ++i) {
                if ((*i)->for_list) {
                    Optimizer::RunParams params(*this, list, depends_from);
                    (*i)->run(params);
                    categories_to_process |= current_affected |= params.ref_affects_to;
                }
            }
        }

        if (for_task || for_root_task) {
            int calls_count = 0;
            int optimizations_count = 0;

            bool nonrecursive = false;

            for (Task::List::iterator j = list.begin(); !(categories_to_process & depends_from) && j != list.end();) {
                if (*j) {
                    Optimizer::RunParams params(*this, list, depends_from, *j);
                    optimize_recursive(current_optimizers, params, calls_count, optimizations_count, !for_task ? 0 : nonrecursive ? 1 : INT_MAX);
                    nonrecursive = false;

                    if (*j != params.ref_task) {

                        if (params.ref_task) {
                            *j = params.ref_task;

                            // go to next sub-task if we don't need to repeat optimization (see Optimizer::MODE_REPEAT)
                            if ((params.ref_mode & Optimizer::MODE_REPEAT_LAST) == Optimizer::MODE_REPEAT_LAST) {
                                // check non-recursive flag (see Optimizer::MODE_RECURSIVE)
                                if (!(params.ref_mode & Optimizer::MODE_RECURSIVE)) {
                                    nonrecursive = true;
                                }
                            } else {
                                ++j;
                            }
                        } else {
                            j = list.erase(j);
                        }
                    } else {
                        ++j;
                    }

                    categories_to_process |= current_affected |= params.ref_affects_to;
                } else {
                    j = list.erase(j);
                }
            }

#ifdef DEBUG_OPTIMIZATION_COUNTERS
            debug::Log::info("", "optimize category %d index %d: calls %d, changes %d",
                             current_category_id, current_optimizer_index, calls_count, optimizations_count);
#endif
        }

        if (categories_to_process & depends_from) {
            current_category_id = 0;
            current_optimizer_index = 0;
            current_affected = 0;
            continue;
        }

        current_optimizer_index += current_optimizers.size();
    }

    // remove nulls
    for (Task::List::iterator j = list.begin(); j != list.end();)
        if (*j) {
            ++j;
        } else {
            j = list.erase(j);
        }
}

void
Renderer::find_deps(const Task::List &list) const
{
    typedef std::map<Surface::Handle, Task::Handle> DepTargetPrevMap;
    DepTargetPrevMap target_prev_map;
    Task::Set tasks_to_process;
    const int max_iterations = 100000;

    // find dependencies by target surface
    for (Task::List::const_iterator i = list.begin(); i != list.end(); ++i) {
        assert((*i)->index == 0);
        (*i)->index = i - list.begin() + 1;

        (*i)->back_deps.clear();
        (*i)->deps_count = 0;

        if ((*i)->valid_target()) {
            for (Task::List::const_iterator j = (*i)->sub_tasks.begin(); j != (*i)->sub_tasks.end(); ++j)
                if (*j && (*j)->valid_target())
                    if (Task::Handle dep = target_prev_map[(*j)->target_surface]) {
                        if ((*i)->tmp_deps.empty()) {
                            tasks_to_process.insert(*i);
                        }

                        (*i)->tmp_deps.insert(dep);
                        dep->tmp_back_deps.insert(*i);
                    }

            Task::Handle &dep = target_prev_map[(*i)->target_surface];

            if (dep) {
                if ((*i)->tmp_deps.empty()) {
                    tasks_to_process.insert(*i);
                }

                (*i)->tmp_deps.insert(dep);
                dep->tmp_back_deps.insert(*i);
            }

            dep = *i;
        }
    }

    // fix dependencies for tasks with non-intersected areas
    int iterations = 0;

    while (iterations < max_iterations && !tasks_to_process.empty()) {
        for (Task::Set::iterator i = tasks_to_process.begin(); i != tasks_to_process.end();) {
            Task::Handle task = *i;
            assert(!task->tmp_deps.empty());

            Task::Handle dep = *task->tmp_deps.begin();
            task->tmp_deps.erase(task->tmp_deps.begin());
            dep->tmp_back_deps.erase(task);

            bool intersects = dep->target_surface == task->target_surface
                              && etl::intersect(dep->get_target_rect(), task->get_target_rect());

            for (Task::List::const_iterator j = task->sub_tasks.begin(); !intersects && j != task->sub_tasks.end(); ++j) {
                if (*j && (*j)->valid_target()
                        && dep->target_surface == (*j)->target_surface
                        && etl::intersect(dep->get_target_rect(), (*j)->get_target_rect())) {
                    intersects = true;
                }

                ++iterations;
            }

            if (intersects) {
                task->deps.insert(dep);
                dep->back_deps.insert(task);
                ++iterations;
            } else {
                iterations += dep->deps.size() + dep->tmp_deps.size() + task->back_deps.size() + task->tmp_back_deps.size();

                for (Task::Set::iterator j = dep->deps.begin(); j != dep->deps.end(); ++j)
                    if (task->deps.count(*j) == 0) {
                        task->tmp_deps.insert(*j);
                        (*j)->tmp_back_deps.insert(task);
                    }

                for (Task::Set::iterator j = dep->tmp_deps.begin(); j != dep->tmp_deps.end(); ++j)
                    if (task->deps.count(*j) == 0) {
                        task->tmp_deps.insert(*j);
                        (*j)->tmp_back_deps.insert(task);
                    }

                for (Task::Set::iterator j = task->back_deps.begin(); j != task->back_deps.end(); ++j)
                    if ((*j)->deps.count(dep) == 0) {
                        if ((*j)->tmp_deps.empty()) {
                            tasks_to_process.insert(*j);
                        }

                        (*j)->tmp_deps.insert(dep);
                        dep->tmp_back_deps.insert(*j);
                    }

                for (Task::Set::iterator j = task->tmp_back_deps.begin(); j != task->tmp_back_deps.end(); ++j)
                    if ((*j)->deps.count(dep) == 0) {
                        (*j)->tmp_deps.insert(dep);
                        dep->tmp_back_deps.insert(*j);
                    }
            }

            Task::Set::iterator j = i;
            ++i;
            ++iterations;

            if (task->tmp_deps.empty()) {
                tasks_to_process.erase(j);
            }
        }
    }

#ifdef DEBUG_TASK_MEASURE
    info("find deps iterations: %d (%d from %d tasks not optimized)", iterations, (int)tasks_to_process.size(), (int)list.size());
#endif

    // copy tmp_deps to deps
    for (Task::List::const_iterator i = list.begin(); i != list.end(); ++i) {
        Task::Handle task = *i;
        task->back_deps.insert(task->tmp_back_deps.begin(), task->tmp_back_deps.end());
        task->deps_count = (int)task->deps.size() + (int)task->tmp_deps.size();

        task->deps.clear();
        task->tmp_deps.clear();
        task->tmp_back_deps.clear();
    }
}

bool
Renderer::run(const Task::List &list) const
{
    // info("renderer.debug.task_list_optimized_log: %s", get_debug_options().task_list_optimized_log.c_str());

#ifndef NDEBUG
    for (Task::List::const_iterator j = list.begin(); j != list.end(); ++j) {
        assert((*j)->check());
    }

#endif

#ifdef DEBUG_TASK_MEASURE
    debug::Measure t("Renderer::run");
#endif

#ifdef DEBUG_TASK_LIST
    log("", list, "input list");
#endif

    if (!get_debug_options().task_list_log.empty()) {
        log(get_debug_options().task_list_log, list, "input list");
    }

    Task::List optimized_list(list);
    {
#ifdef DEBUG_TASK_MEASURE
        debug::Measure t("optimize");
#endif
        optimize(optimized_list);
    }

    {
#ifdef DEBUG_TASK_MEASURE
        debug::Measure t("find deps");
#endif
        find_deps(optimized_list);
    }

#ifdef DEBUG_TASK_LIST
    log("", optimized_list, "optimized list");
#endif

    if (!get_debug_options().task_list_optimized_log.empty()) {
        log(get_debug_options().task_list_optimized_log, optimized_list, "optimized list");
    }

    bool success = true;

    {
#ifdef DEBUG_TASK_MEASURE
        debug::Measure t("run tasks");
#endif

        Glib::Threads::Cond cond;
        Glib::Threads::Mutex mutex;
        Glib::Threads::Mutex::Lock lock(mutex);

        TaskCallbackCond::Handle task_cond = new TaskCallbackCond();
        task_cond->cond = &cond;
        task_cond->mutex = &mutex;

        for (Task::List::const_iterator i = optimized_list.begin(); i != optimized_list.end(); ++i)
            if ((*i)->back_deps.insert(task_cond).second) {
                ++task_cond->deps_count;
            }

        optimized_list.push_back(task_cond);

        queue->enqueue(optimized_list, Task::RunParams(this));

        task_cond->cond->wait(mutex);

        if (!task_cond->success) {
            success = false;
        }

        if (!get_debug_options().result_image.empty())
            debug::DebugSurface::save_to_file(
                !list.empty() && list.back()
                ? list.back()->target_surface
                : Surface::Handle(),
                get_debug_options().result_image,
                true);
    }

    return success;
}

void
Renderer::enqueue(const Task::List &list, const Task::Handle &finish_signal_task) const
{
#ifndef NDEBUG

    for (Task::List::const_iterator j = list.begin(); j != list.end(); ++j) {
        assert((*j)->check());
    }

#endif

    Task::List optimized_list(list);
    optimize(optimized_list);
    find_deps(optimized_list);

    if (finish_signal_task) {
        for (Task::List::const_iterator i = optimized_list.begin(); i != optimized_list.end(); ++i)
            if ((*i)->back_deps.insert(finish_signal_task).second) {
                ++finish_signal_task->deps_count;
            }

        optimized_list.push_back(finish_signal_task);
    }

    queue->enqueue(optimized_list, Task::RunParams(this));
}

void
Renderer::log(
    const String &logfile,
    const Task::Handle &task,
    const Optimizer::RunParams* optimization_stack,
    int level) const
{
    bool use_stack = false;
    const Optimizer::RunParams* p = optimization_stack ? optimization_stack->get_level(level) : NULL;
    Task::Handle t = task;

    if (p && p->orig_task == t) {
        use_stack = true;
        t = p->ref_task;
    }

    if (t) {
        String back_deps;

        if (!t->back_deps.empty()) {
            std::multiset<int> back_deps_set;

            for (Task::Set::const_iterator i = t->back_deps.begin(); i != t->back_deps.end(); ++i) {
                back_deps_set.insert((*i)->index);
            }

            for (std::multiset<int>::const_iterator i = back_deps_set.begin(); i != back_deps_set.end(); ++i) {
                back_deps += etl::strprintf("%d ", *i);
            }

            back_deps = "(" + back_deps.substr(0, back_deps.size() - 1) + ") ";
        }

        debug::Log::info(logfile,
                         String(level * 2, ' ')
                         + (use_stack ? "*" : "")
                         + (t->index ? etl::strprintf("#%d ", t->index) : "")
                         + (t->deps_count
                            ? etl::strprintf("%d ", t->deps_count)
                            : "")
                         + back_deps
                         + (typeid(*t).name() + 19)
                         + (t->get_bounds().valid()
                            ? etl::strprintf(" bounds (%f, %f)-(%f, %f)",
                                             t->get_bounds().minx, t->get_bounds().miny,
                                             t->get_bounds().maxx, t->get_bounds().maxy)
                            : "")
                         + (t->valid_target()
                            ? etl::strprintf(" source (%f, %f)-(%f, %f) target (%d, %d)-(%d, %d) surface %s (%dx%d) id %d",
                                             t->get_source_rect_lt()[0], t->get_source_rect_lt()[1],
                                             t->get_source_rect_rb()[0], t->get_source_rect_rb()[1],
                                             t->get_target_rect().minx, t->get_target_rect().miny,
                                             t->get_target_rect().maxx, t->get_target_rect().maxy,
                                             (typeid(*t->target_surface).name() + 19),
                                             t->target_surface->get_width(),
                                             t->target_surface->get_height(),
                                             t->target_surface->get_id())
                            : ""));

        for (Task::List::const_iterator i = t->sub_tasks.begin(); i != t->sub_tasks.end(); ++i) {
            log(logfile, *i, use_stack ? optimization_stack : NULL, level + 1);
        }
    } else {
        debug::Log::info(logfile,
                         String(level * 2, ' ')
                         + (use_stack ? "*" : "")
                         + "NULL");
    }
}

void
Renderer::log(
    const String &logfile,
    const Task::List &list,
    const String &name,
    const Optimizer::RunParams* optimization_stack) const
{
    String line = "-------------------------------------------";
    String n = "    " + name;
    n.resize(line.size(), ' ');

    for (int i = 0; i < (int)line.size(); ++i)
        if (n[i] == ' ') {
            n[i] = line[i];
        }

    debug::Log::info(logfile, n);

    for (Task::List::const_iterator i = list.begin(); i != list.end(); ++i) {
        log(logfile, *i, optimization_stack);
    }

    debug::Log::info(logfile, line);
}

void
Renderer::initialize()
{
    if (renderers != NULL || queue != NULL) {
        synfig::error("rendering::Renderer already initialized");
    }

    // init debug options
    if (const char *s = getenv("SYNFIG_RENDERING_DEBUG_TASK_LIST_LOG")) {
        debug_options.task_list_log = s;
    }

    if (const char *s = getenv("SYNFIG_RENDERING_DEBUG_TASK_LIST_OPTIMIZED_LOG")) {
        debug_options.task_list_optimized_log = s;
    }

    if (const char *s = getenv("SYNFIG_RENDERING_DEBUG_RESULT_IMAGE")) {
        debug_options.result_image = s;
    }

    renderers = new std::map<String, Handle>();
    queue = new RenderQueue();

    initialize_renderers();
}

void
Renderer::deinitialize()
{
    if (renderers == NULL || queue == NULL) {
        synfig::error("rendering::Renderer not initialized");
    }

    while (!get_renderers().empty()) {
        unregister_renderer(get_renderers().begin()->first);
    }

    deinitialize_renderers();

    delete renderers;
    delete queue;
}

void
Renderer::register_renderer(const String &name, const Renderer::Handle &renderer)
{
    if (renderers->count(name)) {
        synfig::error("rendering::Renderer renderer '%s' already registered", name.c_str());
    }

    (*renderers)[name] = renderer;
}

void
Renderer::unregister_renderer(const String &name)
{
    if (!renderers->count(name)) {
        synfig::error("rendering::Renderer renderer '%s' not registered", name.c_str());
    }

    renderers->erase(name);
}

const Renderer::Handle&
Renderer::get_renderer(const String &name)
{
    static const char default_engine[] = "software";
    return get_renderers().count(name) > 0           ? get_renderers().find(name)->second
           : get_renderers().count(default_engine) > 0 ? get_renderers().find(default_engine)->second
           : blank;
}

const std::map<String, Renderer::Handle>&
Renderer::get_renderers()
{
    if (renderers == NULL) {
        synfig::error("rendering::Renderer not initialized");
    }

    return *renderers;
}